/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.code;

import com.android.dx.rop.code.LocalItem;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.type.Type;
import com.android.dx.util.FixedSizeList;

/**
 * List of "local variable" entries, which are the contents of
 * {@code LocalVariableTable} and {@code LocalVariableTypeTable} attributes, as
 * well as combinations of the two.
 */
public final class LocalVariableList extends FixedSizeList {

	/** {@code non-null;} zero-size instance */
	public static final LocalVariableList EMPTY = new LocalVariableList(0);

	/**
	 * Returns an instance which is the concatenation of the two given
	 * instances. The result is immutable.
	 * 
	 * @param list1
	 *            {@code non-null;} first instance
	 * @param list2
	 *            {@code non-null;} second instance
	 * @return {@code non-null;} combined instance
	 */
	public static LocalVariableList concat(LocalVariableList list1,
			LocalVariableList list2) {
		if (list1 == EMPTY) {
			// easy case
			return list2;
		}

		int sz1 = list1.size();
		int sz2 = list2.size();
		LocalVariableList result = new LocalVariableList(sz1 + sz2);

		for (int i = 0; i < sz1; i++) {
			result.set(i, list1.get(i));
		}

		for (int i = 0; i < sz2; i++) {
			result.set(sz1 + i, list2.get(i));
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Returns an instance which is the result of merging the two given
	 * instances, where one instance should have only type descriptors and the
	 * other only type signatures. The merged result is identical to the one
	 * with descriptors, except that any element whose {name, index, start,
	 * length} matches an element in the signature list gets augmented with the
	 * corresponding signature. The result is immutable.
	 * 
	 * @param descriptorList
	 *            {@code non-null;} list with descriptors
	 * @param signatureList
	 *            {@code non-null;} list with signatures
	 * @return {@code non-null;} the merged result
	 */
	public static LocalVariableList mergeDescriptorsAndSignatures(
			LocalVariableList descriptorList, LocalVariableList signatureList) {
		int descriptorSize = descriptorList.size();
		LocalVariableList result = new LocalVariableList(descriptorSize);

		for (int i = 0; i < descriptorSize; i++) {
			Item item = descriptorList.get(i);
			Item signatureItem = signatureList.itemToLocal(item);
			if (signatureItem != null) {
				CstString signature = signatureItem.getSignature();
				item = item.withSignature(signature);
			}
			result.set(i, item);
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Constructs an instance.
	 * 
	 * @param count
	 *            the number of elements to be in the list
	 */
	public LocalVariableList(int count) {
		super(count);
	}

	/**
	 * Gets the indicated item.
	 * 
	 * @param n
	 *            {@code >= 0;} which item
	 * @return {@code null-ok;} the indicated item
	 */
	public Item get(int n) {
		return (Item) get0(n);
	}

	/**
	 * Sets the item at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which element
	 * @param item
	 *            {@code non-null;} the item
	 */
	public void set(int n, Item item) {
		if (item == null) {
			throw new NullPointerException("item == null");
		}

		set0(n, item);
	}

	/**
	 * Sets the item at the given index.
	 * <p>
	 * <b>Note:</b> At least one of {@code descriptor} or {@code signature} must
	 * be passed as non-null.
	 * </p>
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which element
	 * @param startPc
	 *            {@code >= 0;} the start pc of this variable's scope
	 * @param length
	 *            {@code >= 0;} the length (in bytecodes) of this variable's
	 *            scope
	 * @param name
	 *            {@code non-null;} the variable's name
	 * @param descriptor
	 *            {@code null-ok;} the variable's type descriptor
	 * @param signature
	 *            {@code null-ok;} the variable's type signature
	 * @param index
	 *            {@code >= 0;} the variable's local index
	 */
	public void set(int n, int startPc, int length, CstString name,
			CstString descriptor, CstString signature, int index) {
		set0(n, new Item(startPc, length, name, descriptor, signature, index));
	}

	/**
	 * Gets the local variable information in this instance which matches the
	 * given {@link com.android.dx.cf.code.LocalVariableList.Item} in all
	 * respects but the type descriptor and signature, if any.
	 * 
	 * @param item
	 *            {@code non-null;} local variable information to match
	 * @return {@code null-ok;} the corresponding local variable information
	 *         stored in this instance, or {@code null} if there is no matching
	 *         information
	 */
	public Item itemToLocal(Item item) {
		int sz = size();

		for (int i = 0; i < sz; i++) {
			Item one = (Item) get0(i);

			if ((one != null) && one.matchesAllButType(item)) {
				return one;
			}
		}

		return null;
	}

	/**
	 * Gets the local variable information associated with a given address and
	 * local index, if any. <b>Note:</b> In standard classfiles, a variable's
	 * start point is listed as the address of the instruction <i>just past</i>
	 * the one that sets the variable.
	 * 
	 * @param pc
	 *            {@code >= 0;} the address to look up
	 * @param index
	 *            {@code >= 0;} the local variable index
	 * @return {@code null-ok;} the associated local variable information, or
	 *         {@code null} if none is known
	 */
	public Item pcAndIndexToLocal(int pc, int index) {
		int sz = size();

		for (int i = 0; i < sz; i++) {
			Item one = (Item) get0(i);

			if ((one != null) && one.matchesPcAndIndex(pc, index)) {
				return one;
			}
		}

		return null;
	}

	/**
	 * Item in a local variable table.
	 */
	public static class Item {

		/** {@code >= 0;} the start pc of this variable's scope */
		private final int startPc;

		/** {@code >= 0;} the length (in bytecodes) of this variable's scope */
		private final int length;

		/** {@code non-null;} the variable's name */
		private final CstString name;

		/** {@code null-ok;} the variable's type descriptor */
		private final CstString descriptor;

		/** {@code null-ok;} the variable's type signature */
		private final CstString signature;

		/** {@code >= 0;} the variable's local index */
		private final int index;

		/**
		 * Constructs an instance.
		 * <p>
		 * <b>Note:</b> At least one of {@code descriptor} or {@code signature}
		 * must be passed as non-null.
		 * </p>
		 * 
		 * @param startPc
		 *            {@code >= 0;} the start pc of this variable's scope
		 * @param length
		 *            {@code >= 0;} the length (in bytecodes) of this variable's
		 *            scope
		 * @param name
		 *            {@code non-null;} the variable's name
		 * @param descriptor
		 *            {@code null-ok;} the variable's type descriptor
		 * @param signature
		 *            {@code null-ok;} the variable's type signature
		 * @param index
		 *            {@code >= 0;} the variable's local index
		 */
		public Item(int startPc, int length, CstString name,
				CstString descriptor, CstString signature, int index) {
			if (startPc < 0) {
				throw new IllegalArgumentException("startPc < 0");
			}

			if (length < 0) {
				throw new IllegalArgumentException("length < 0");
			}

			if (name == null) {
				throw new NullPointerException("name == null");
			}

			if ((descriptor == null) && (signature == null)) {
				throw new NullPointerException(
						"(descriptor == null) && (signature == null)");
			}

			if (index < 0) {
				throw new IllegalArgumentException("index < 0");
			}

			this.startPc = startPc;
			this.length = length;
			this.name = name;
			this.descriptor = descriptor;
			this.signature = signature;
			this.index = index;
		}

		/**
		 * Gets the start pc of this variable's scope.
		 * 
		 * @return {@code >= 0;} the start pc of this variable's scope
		 */
		public int getStartPc() {
			return startPc;
		}

		/**
		 * Gets the length (in bytecodes) of this variable's scope.
		 * 
		 * @return {@code >= 0;} the length (in bytecodes) of this variable's
		 *         scope
		 */
		public int getLength() {
			return length;
		}

		/**
		 * Gets the variable's type descriptor.
		 * 
		 * @return {@code null-ok;} the variable's type descriptor
		 */
		public CstString getDescriptor() {
			return descriptor;
		}

		/**
		 * Gets the variable's LocalItem, a (name, signature) tuple
		 * 
		 * @return {@code null-ok;} the variable's type descriptor
		 */
		public LocalItem getLocalItem() {
			return LocalItem.make(name, signature);
		}

		/**
		 * Gets the variable's type signature. Private because if you need this,
		 * you want getLocalItem() instead.
		 * 
		 * @return {@code null-ok;} the variable's type signature
		 */
		private CstString getSignature() {
			return signature;
		}

		/**
		 * Gets the variable's local index.
		 * 
		 * @return {@code >= 0;} the variable's local index
		 */
		public int getIndex() {
			return index;
		}

		/**
		 * Gets the variable's type descriptor. This is a convenient shorthand
		 * for {@code Type.intern(getDescriptor().getString())}.
		 * 
		 * @return {@code non-null;} the variable's type
		 */
		public Type getType() {
			return Type.intern(descriptor.getString());
		}

		/**
		 * Constructs and returns an instance which is identical to this one,
		 * except that the signature is changed to the given value.
		 * 
		 * @param newSignature
		 *            {@code non-null;} the new signature
		 * @return {@code non-null;} an appropriately-constructed instance
		 */
		public Item withSignature(CstString newSignature) {
			return new Item(startPc, length, name, descriptor, newSignature,
					index);
		}

		/**
		 * Gets whether this instance matches (describes) the given address and
		 * index.
		 * 
		 * @param pc
		 *            {@code >= 0;} the address in question
		 * @param index
		 *            {@code >= 0;} the local variable index in question
		 * @return {@code true} iff this instance matches {@code pc} and
		 *         {@code index}
		 */
		public boolean matchesPcAndIndex(int pc, int index) {
			return (index == this.index) && (pc >= startPc)
					&& (pc < (startPc + length));
		}

		/**
		 * Gets whether this instance matches (describes) the given other
		 * instance exactly in all fields except type descriptor and type
		 * signature.
		 * 
		 * @param other
		 *            {@code non-null;} the instance to compare to
		 * @return {@code true} iff this instance matches
		 */
		public boolean matchesAllButType(Item other) {
			return (startPc == other.startPc) && (length == other.length)
					&& (index == other.index) && name.equals(other.name);
		}
	}
}
